﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Shapes.Geometry;

namespace Shapes.Misc
{

    /// <summary>
    /// The EventArgs of a collision holding the active and passive geometry
    /// </summary>
    public class CollisionEventArgs : EventArgs
    {
        /// <summary>
        /// the active geometry, which has caused the detection test
        /// </summary>
        public Drawing ActiveGeometry { get; private set; }

        /// <summary>
        /// the passive geometry which collides with the active one
        /// </summary>
        public Drawing PassiveGeometry { get; private set; }

        internal CollisionEventArgs(Drawing active, Drawing passive)
            : base()
        {
            ActiveGeometry = active;
            PassiveGeometry = passive;
        }
    }

    /// <summary>
    /// A manager class to test collisions between 2D geometry
    /// </summary>
    public class CollisionDetector2D
    {
        List<Drawing> _ActiveGeometry = new List<Drawing>();
        List<Drawing> _PassiveGeometry = new List<Drawing>();

        //List<Shape> _ActiveShapes = new List<Shape>();
        //List<Shape> _PassiveShapes = new List<Shape>();

        /// <summary>
        /// This event is called when a collision has been detected. 
        /// If there are more than one different passive objects colliding at the same time with the same active object, 
        /// only the first detected collision will call this event
        /// </summary>
        public event EventHandler<CollisionEventArgs> CollisionDetected;

        float _Accuracy = 1;
        /// <summary>
        /// The accuracy of the collision tests. Use 1.0 for a per-pixel test (the higher the less accurate)
        /// </summary>
        public float Accuracy { get { return _Accuracy; } set { _Accuracy = value; } }

        /// <summary>
        /// creates a new CollisionDetector2d
        /// </summary>
        public CollisionDetector2D()
        { }
        /// <summary>
        /// creates a new CollisionDetector2d
        /// </summary>
        /// <param name="accuracy">The accuracy of the collision tests. Use 1.0 for a per-pixel test (the higher the less accurate)</param>
        public CollisionDetector2D(float accuracy)
        {
            _Accuracy = accuracy;
        }

        /// <summary>
        /// introduces a shape to the collision detector
        /// </summary>
        /// <param name="geometry">a drawing or shape to add</param>
        /// <param name="isActive">true, if the geometry should do active tests if it collides with something passive</param>
        /// <param name="isPassive">true, if the geometry is a collidable object in the level</param>
        [Obsolete("use SetupGeometry() instead")]
        public void Add(Drawing geometry, bool isActive, bool isPassive)
        {
            SetupGeometry(geometry, isActive, isPassive);
        }

        /// <summary>
        /// Removes a geometry from the collision detector
        /// </summary>
        /// <param name="geometry">the drawing or shape to remove</param>
        public void Remove(Drawing geometry)
        {
            bool isShape = (geometry is Shape);
            // active
            //if (isShape && (_ActiveShapes.Contains(geometry as Shape)))
            //    _ActiveShapes.Remove(geometry as Shape);
            //else 
            if (_ActiveGeometry.Contains(geometry))
                _ActiveGeometry.Remove(geometry);
            // passive
            //if (isShape && (_PassiveShapes.Contains(geometry as Shape)))
            //    _PassiveShapes.Remove(geometry as Shape);
            //else 
             if (_PassiveGeometry.Contains(geometry))
                _PassiveGeometry.Remove(geometry);
        }

        /// <summary>
        /// Changes the active- or passiveness of a geometry
        /// </summary>
        /// <param name="geometry">the drawing or shape to change</param>
        /// <param name="isActive">true, if the geometry should do active tests if it collides with something passive</param>
        /// <param name="isPassive">true, if the geometry is a collidable object in the level</param>
        [Obsolete("use SetupGeometry() instead")]
        public void ChangeMode(Drawing geometry, bool isActive, bool isPassive)
        {
            SetupGeometry(geometry, isActive, isPassive);
        }
        /// <summary>
        /// Adds the geometry to the active or passive geometry list
        /// </summary>
        /// <param name="geometry">the drawing or shape</param>
        /// <param name="isActive">true, if the geometry should do active tests if it collides with something passive</param>
        /// <param name="isPassive">true, if the geometry is a collidable object in the level</param>
        public void SetupGeometry(Drawing geometry, bool isActive, bool isPassive)
        {
            //bool isShape = (geometry is Shape);
            if (isActive)
            {
                //if (isShape && (!_ActiveShapes.Contains(geometry as Shape)))
                //    _ActiveShapes.Add(geometry as Shape);
                //else 
                if (!_ActiveGeometry.Contains(geometry))
                    _ActiveGeometry.Add(geometry);
            }
            else
            {
                //if (isShape && (_ActiveShapes.Contains(geometry as Shape)))
                //    _ActiveShapes.Remove(geometry as Shape);
                //else 
                if (_ActiveGeometry.Contains(geometry))
                    _ActiveGeometry.Remove(geometry);
            }

            if (isPassive)
            {
                //if (isShape && (!_PassiveShapes.Contains(geometry as Shape)))
                //    _PassiveShapes.Add(geometry as Shape);
                //else
                if (!_PassiveGeometry.Contains(geometry))
                    _PassiveGeometry.Add(geometry);
            }
            else
            {
                //if (isShape && (_PassiveShapes.Contains(geometry as Shape)))
                //    _PassiveShapes.Remove(geometry as Shape);
                //else 
                if (_PassiveGeometry.Contains(geometry))
                    _PassiveGeometry.Remove(geometry);
            }
        }

        /// <summary>
        /// Update checks wether any active geometry collides with any passive geometry
        /// </summary>
        [Obsolete("Use CheckCollision instead")]
        public void Update()
        {
            CheckCollision();
        }
        /// <summary>
        /// Checks wether any active geometry collides with any passive geometry.
        /// Calls the CollisionDetected callback when something collides.
        /// </summary>
        /// <returns>returns true when anything collides with anything else.</returns>
        public bool CheckCollision()
        {
            bool collided = false;
            #region old code
            /*
            // check active drawings for collision
            for(int i = 0; i < _ActiveGeometry.Count; i++)
            {
                for(int j = 0; j < _PassiveGeometry.Count; j++)
                {
                    if (_ActiveGeometry[i] == _PassiveGeometry[j])
                        continue;

                    if (DrawingDrawingIntersecting(_ActiveGeometry[i], _PassiveGeometry[j], _Accuracy))
                    {
                        OnCollisionDetected(_ActiveGeometry[i], _PassiveGeometry[j]);
                        collided = true;
                    }

                }
                for (int j = 0; j < _PassiveShapes.Count; j++)
                {
                    if (DrawingShapeIntersecting(_ActiveGeometry[i], _PassiveShapes[j], _Accuracy))
                    {
                        OnCollisionDetected(_ActiveGeometry[i], _PassiveShapes[j]);
                        collided = true;
                    }
                }

            }

            // check active shapes for collision
            for (int i = 0; i < _ActiveShapes.Count; i++)
            {
                for (int j = 0; j < _PassiveGeometry.Count; j++)
                {
                    if (DrawingShapeIntersecting(_PassiveGeometry[j], _ActiveShapes[i], _Accuracy))
                    {
                        OnCollisionDetected(_ActiveShapes[i], _PassiveGeometry[j]);
                        collided = true;
                    }
                }
                for (int j = 0; j < _PassiveShapes.Count; j++)
                {
                    if (_PassiveShapes[j] == _ActiveShapes[i])
                        continue;

                    if (ShapeShapeIntersecting(_ActiveShapes[i], _PassiveShapes[j], _Accuracy))
                    {
                        OnCollisionDetected(_ActiveShapes[i], _PassiveShapes[j]);
                        collided = true;
                    }
                }
            }
            */
            #endregion

            for (int a = 0; a < _ActiveGeometry.Count; a++)
            {
                for (int p = 0; p < _PassiveGeometry.Count; p++)
                {
                    Drawing active = _ActiveGeometry[a];
                    Drawing passive = _PassiveGeometry[p];
                    if (active == passive)
                        continue;

                    if(CallCollisionMethod(active, passive))
                    {
                        OnCollisionDetected(active, passive);
                        collided = true;
                    }
                }
            }

            return collided;
        }

        private bool CallCollisionMethod(Drawing a, Drawing b)
        {

            if (!a._Transform.Intersects(b._Transform))
                return false;

            GeometryType aType = a.GetGeometryType();
            GeometryType bType = b.GetGeometryType();

            switch(aType)
            {
                case GeometryType.Circle:
                    switch (bType)
                    {
                        case GeometryType.Circle:
                            return CircleCircleIntersection(a as Ellipse, b as Ellipse);
                        case GeometryType.Line:
                            return CircleLineIntersection(a as Ellipse, b as Line);
                        case GeometryType.LineStrip:
                            return CircleLinedGeometryIntersection(a as Ellipse, b as ILinedGeometry);
                        case GeometryType.Triangle:
                        case GeometryType.Polygon:
                            return CircleLinedShapeIntersection(a as Ellipse, b as ILinedShape);
                        case GeometryType.Rect:
                            return RectCircleIntersection(b as Rect, a as Ellipse);

                        default:
                            return CheckUndefinedGeometryIntersection(a, b);
                    }
                case GeometryType.Line:
                    switch (bType)
                    {
                        case GeometryType.Circle:
                            return CircleLineIntersection(b as Ellipse, a as Line);
                        case GeometryType.Line:
                            return LineLineIntersection(a as Line, b as Line);
                        case GeometryType.LineStrip:
                            return LinedGeometryLineIntersection(b as ILinedGeometry, a as Line);
                        case GeometryType.Triangle:
                        case GeometryType.Polygon:
                            return LinedShapeLineIntersection(b as ILinedShape, a as Line);
                        case GeometryType.Rect:
                            return RectLineIntersection(b as Rect, a as Line);

                        default:
                            return CheckUndefinedGeometryIntersection(a, b);
                    }
                case GeometryType.LineStrip:
                    switch (bType)
                    {
                        case GeometryType.Circle:
                            return CircleLinedGeometryIntersection(b as Ellipse, a as ILinedGeometry);
                        case GeometryType.Line:
                            return LinedGeometryLineIntersection(a as ILinedGeometry, b as Line);
                        case GeometryType.LineStrip:
                            return LineGeometryLinedGeometryIntersection(a as ILinedGeometry, b as ILinedGeometry);
                        case GeometryType.Triangle:
                        case GeometryType.Polygon:
                            return LinedShapeLinedGeometryIntersection(b as ILinedShape, a as ILinedGeometry);
                        case GeometryType.Rect:
                            return RectLinedGeometryIntersection(b as Rect, a as ILinedGeometry);

                        default:
                            return CheckUndefinedGeometryIntersection(a, b);
                    }
                case GeometryType.Triangle:
                case GeometryType.Polygon:
                    switch (bType)
                    {
                        case GeometryType.Circle:
                            return CircleLinedShapeIntersection(b as Ellipse, a as ILinedShape);
                        case GeometryType.Line:
                            return LinedShapeLineIntersection(a as ILinedShape, b as Line);
                        case GeometryType.LineStrip:
                            return LinedShapeLinedGeometryIntersection(a as ILinedShape, b as ILinedGeometry);
                        case GeometryType.Triangle:
                        case GeometryType.Polygon:
                            return LinedShapeLinedShapeIntersection(a as ILinedShape, b as ILinedShape);
                        case GeometryType.Rect:
                            return RectLinedShapeIntersection(b as Rect, a as ILinedShape);

                        default:
                            return CheckUndefinedGeometryIntersection(a, b);
                    }
                case GeometryType.Rect:
                    switch (bType)
                    {
                        case GeometryType.Circle:
                            return RectCircleIntersection(a as Rect, b as Ellipse);
                        case GeometryType.Line:
                            return RectLineIntersection(a as Rect, b as Line);
                        case GeometryType.LineStrip:
                            return RectLinedGeometryIntersection(a as Rect, b as ILinedGeometry);
                        case GeometryType.Triangle:
                        case GeometryType.Polygon:
                            return RectLinedShapeIntersection(a as Rect, b as ILinedShape);
                        case GeometryType.Rect:
                            return RectRectIntersection(a as Rect, b as Rect);

                        default:
                            return CheckUndefinedGeometryIntersection(a, b);
                    }
                
                default:
                    return CheckUndefinedGeometryIntersection(a, b);                 

            }
        }

        private bool CheckUndefinedGeometryIntersection(Drawing a, Drawing b)
        {
            int aTypeInt = (int)a.GetGeometryType();
            int bTypeInt = (int)b.GetGeometryType();

            if (aTypeInt != 0 && bTypeInt != 0) // 0 = undefined
            {
                int shapeStart = (int)GeometryType.Circle; // hack: circle is the first shape in the enum
                if (aTypeInt < shapeStart && bTypeInt < shapeStart)
                    return DrawingDrawingIntersecting(a, b, Accuracy);
                else if (aTypeInt < shapeStart)
                    return DrawingShapeIntersecting(a, b as Shape, Accuracy);
                else if (bTypeInt < shapeStart)
                    return DrawingShapeIntersecting(b, a as Shape, Accuracy);
                else
                    return ShapeShapeIntersecting(a as Shape, b as Shape, Accuracy);
            }
            else
            {
                Shape shapeA = (a as Shape);
                Shape shapeB = (b as Shape);
                if (shapeA != null && shapeB != null)
                    return ShapeShapeIntersecting(shapeA, shapeB, Accuracy);
                else if (shapeA != null)
                    return DrawingShapeIntersecting(b, shapeA, Accuracy);
                else if (shapeB != null)
                    return DrawingShapeIntersecting(a, shapeB, Accuracy);
                else
                    return DrawingDrawingIntersecting(a, b, Accuracy);
            }
        }
        private void OnCollisionDetected(Drawing activeGeometry, Drawing passiveGeometry)
        {
            if (CollisionDetected != null)
                CollisionDetected(this, new CollisionEventArgs(activeGeometry, passiveGeometry));
        }

        /// <summary>
        /// Releases all geometry references
        /// </summary>
        public void Delete()
        {
            _ActiveGeometry.Clear();
            _ActiveGeometry = null;

            _PassiveGeometry.Clear();
            _PassiveGeometry = null;

            //_ActiveShapes.Clear();
            //_ActiveShapes = null;

            //_PassiveShapes.Clear();
            //_PassiveShapes = null;
        }


        #region Collision Test Methods
        /// <summary>
        /// Does a test if a drawing collides with another drawing
        /// </summary>
        /// <param name="drawingA">one of the drawings to test</param>
        /// <param name="drawingB">the other drawing to test</param>
        /// <returns>true, if they collide</returns>
        public static bool DrawingDrawingIntersecting(Drawing drawingA, Drawing drawingB)
        {
            return DrawingDrawingIntersecting(drawingA, drawingB, 1);
        }
        /// <summary>
        /// Does a test if a drawing collides with another drawing
        /// </summary>
        /// <param name="drawingA">one of the drawings to test</param>
        /// <param name="drawingB">the other drawing to test</param>
        /// <param name="accuracy">The accuracy of the test. Use 1.0 for a per-pixel test (the higher the value the less accurate)</param>
        /// <returns>true, if they collide</returns>
        public static bool DrawingDrawingIntersecting(Drawing drawingA, Drawing drawingB, float accuracy)
        {

            Vector2 pos = new Vector2(
                Math.Max(drawingA.Position.X, drawingB.Position.X),
                Math.Max(drawingA.Position.Y, drawingB.Position.Y));

            Transformation2D overlap = new Transformation2D(pos,
                    Math.Abs(pos.X - Math.Min(drawingA.Position.X + drawingA.Width, drawingB.Position.X + drawingB.Width)),
                    Math.Abs(pos.Y - Math.Min(drawingA.Position.Y + drawingA.Height, drawingB.Position.Y + drawingB.Height)));

            float maxX = overlap._Position.X + overlap._Width;
            float maxY = overlap._Position.Y + overlap._Height;

            while (pos.X <= maxX)
            {
                pos.Y = overlap._Position.Y;
                while (pos.Y <= maxY)
                {
                    if (drawingA.GetDistanceToEdge(ref pos) <= accuracy)
                        if (drawingB.GetDistanceToEdge(ref pos) <= accuracy)
                            return true;

                    pos.Y += accuracy;
                }
                pos.X += accuracy;
            }
            return false;
        }
        /// <summary>
        /// Does a test if a drawing collides with a shape
        /// </summary>
        /// <param name="drawing">the drawing to test</param>
        /// <param name="shape">the shape to test</param>
        /// <returns>true, if they collide</returns>
        public static bool DrawingShapeIntersecting(Drawing drawing, Shape shape)
        {
            return DrawingShapeIntersecting(drawing, shape, 1);
        }
        /// <summary>
        /// Does a test if a drawing collides with a shape
        /// </summary>
        /// <param name="drawing">the drawing to test</param>
        /// <param name="shape">the shape to test</param>
        /// <param name="accuracy">The accuracy of the test. Use 1.0 for a per-pixel test (the higher the value the less accurate)</param>
        /// <returns>true, if they collide</returns>
        public static bool DrawingShapeIntersecting(Drawing drawing, Shape shape, float accuracy)
        {

            Vector2 pos = new Vector2(
                Math.Max(drawing._Transform.Left, shape._Transform.Left),
                Math.Max(drawing._Transform.Up, shape._Transform.Up));

            float tmpY = pos.Y;
            float maxX = Math.Min(drawing._Transform.Right, shape._Transform.Right);
            float maxY = Math.Min(drawing._Transform.Down, shape._Transform.Down);

            while (pos.X <= maxX)
            {
                pos.Y = tmpY;
                while (pos.Y <= maxY)
                {
                    if (shape.IsPointInside(pos))
                        if (drawing.GetDistanceToEdge(pos) <= accuracy)
                            return true;

                    pos.Y += accuracy;
                }
                pos.X += accuracy;
            }
            return false;
        }

        /// <summary>
        /// Does a test if a shape collides with another shape
        /// </summary>
        /// <param name="shapeA">one of the shapes to test</param>
        /// <param name="shapeB">the other shape to test</param>
        /// <returns>true, if they collide</returns>
        public static bool ShapeShapeIntersecting(Shape shapeA, Shape shapeB)
        {
            return ShapeShapeIntersecting(shapeA, shapeB, 1);
        }
        /// <summary>
        /// Does a test if a shape collides with another shape
        /// </summary>
        /// <param name="shapeA">one of the shapes to test</param>
        /// <param name="shapeB">the other shape to test</param>
        /// <param name="accuracy">The accuracy of the test. Use 1.0f for a per-pixel test (the higher the value the less accurate)</param>
        /// <returns>true, if they collide</returns>
        public static bool ShapeShapeIntersecting(Shape shapeA, Shape shapeB, float accuracy)
        {
            Vector2 pos = new Vector2(
                Math.Max(shapeA._Transform.Left, shapeB._Transform.Left),
                Math.Max(shapeA._Transform.Up, shapeB._Transform.Up));

            float tmpY = pos.Y;
            float maxX = Math.Min(shapeA._Transform.Right, shapeB._Transform.Right);
            float maxY = Math.Min(shapeA._Transform.Down, shapeB._Transform.Down);

            while(pos.X <= maxX)
            {
                pos.Y = tmpY;
                while(pos.Y <= maxY)
                {
                    if (shapeA.IsPointInside(pos))
                        if(shapeB.IsPointInside(pos))
                            return true;

                    pos.Y += accuracy;
                }
                pos.X += accuracy;
            }
            return false;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool CircleCircleIntersection(Ellipse circleA, Ellipse circleB)
        {
            if (circleA.GetGeometryType() != GeometryType.Circle || circleB.GetGeometryType() != GeometryType.Circle)
                throw new ArgumentException("at least one of the passed shapes is no circle");


            float doubleRad = circleA.Scale.X * circleA.HorizontalRadius + circleB.Scale.X * circleB.HorizontalRadius;
            return Vector2.DistanceSquared(circleA.Center, circleB.Center) <= doubleRad * doubleRad;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool CircleLineIntersection(Ellipse circle, Line line)
        {
            if (circle.GetGeometryType() != GeometryType.Circle)
                throw new ArgumentException("the passed ellipse is no circle");

            Vector2 start = line.StartPoint;
            Vector2 end = line.EndPoint;
            return CircleLineIntersection(circle, ref start, ref end);
        }
        private static bool CircleLineIntersection(Ellipse circle, ref Vector2 lineStart, ref Vector2 lineEnd)
        {
            Vector2 center = circle.Center;
            Vector2 p = Line.GetClosestPoint(ref center, ref lineStart, ref lineEnd);

            return Vector2.DistanceSquared(center, p) <= circle.Scale.X * circle.HorizontalRadius * circle.Scale.Y * circle.VerticalRadius;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool CircleLinedGeometryIntersection(Ellipse circle, ILinedGeometry linedGeometry)
        {
            if (circle.GetGeometryType() != GeometryType.Circle)
                throw new ArgumentException("the passed ellipse is no circle");

            foreach (Line l in linedGeometry.GetLines())
            {
                Vector2 start = linedGeometry.Transform.TransformLocalToGlobal(l.StartPoint);
                Vector2 end = linedGeometry.Transform.TransformLocalToGlobal(l.EndPoint);

                if (CircleLineIntersection(circle, ref start, ref end))
                    return true;
            }
            return false;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool CircleLinedShapeIntersection(Ellipse circle, ILinedShape linedShape)
        {
            if (CircleLinedGeometryIntersection(circle, linedShape))
                return true;

            return linedShape.IsPointInside(circle.Center);
        }

        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool RectRectIntersection(Rect rectA, Rect rectB)
        {
            if(rectA._Transform._Rotation.Radians == 0 && rectB._Transform._Rotation.Radians == 0)
                return rectA._Transform.GetBoundingRectangle().Intersects(rectB._Transform.GetBoundingRectangle());

            Vector2 a1 = rectA._Transform.TransformLocalToGlobal(Vector2.Zero);
            Vector2 b1 = rectB._Transform.TransformLocalToGlobal(Vector2.Zero);

            if (rectA.IsPointInside(b1) || rectB.IsPointInside(a1))
                return true;

            Vector2 a2 = rectA._Transform.TransformLocalToGlobal(new Vector2(rectA.Width, 0));
            Vector2 a3 = rectA._Transform.TransformLocalToGlobal(new Vector2(rectA.Width, rectA.Height));
            Vector2 a4 = rectA._Transform.TransformLocalToGlobal(new Vector2(0, rectA.Height));
                        
            Vector2 b2 = rectB._Transform.TransformLocalToGlobal(new Vector2(rectB.Width, 0));
            Vector2 b3 = rectB._Transform.TransformLocalToGlobal(new Vector2(rectB.Width, rectB.Height));
            Vector2 b4 = rectB._Transform.TransformLocalToGlobal(new Vector2(0, rectB.Height));

            return LineLineIntersection(ref a1, ref a2, ref b1, ref b2)
                || LineLineIntersection(ref a1, ref a2, ref b2, ref b3)
                || LineLineIntersection(ref a1, ref a2, ref b3, ref b4)
                || LineLineIntersection(ref a1, ref a2, ref b4, ref b1)

                || LineLineIntersection(ref a2, ref a3, ref b1, ref b2)
                || LineLineIntersection(ref a2, ref a3, ref b2, ref b3)
                || LineLineIntersection(ref a2, ref a3, ref b3, ref b4)
                || LineLineIntersection(ref a2, ref a3, ref b4, ref b1)

                || LineLineIntersection(ref a3, ref a4, ref b1, ref b2)
                || LineLineIntersection(ref a3, ref a4, ref b2, ref b3)
                || LineLineIntersection(ref a3, ref a4, ref b3, ref b4)
                || LineLineIntersection(ref a3, ref a4, ref b4, ref b1)

                || LineLineIntersection(ref a4, ref a1, ref b1, ref b2)
                || LineLineIntersection(ref a4, ref a1, ref b2, ref b3)
                || LineLineIntersection(ref a4, ref a1, ref b3, ref b4)
                || LineLineIntersection(ref a4, ref a1, ref b4, ref b1);
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool RectCircleIntersection(Rect rect, Ellipse circle)
        {
            if(circle.GetGeometryType() != GeometryType.Circle)
                throw new ArgumentException("the passed ellipse is no circle");

            Vector2 point = rect.GetNearestPointOnEdge(circle.Center);
            float radius = (circle.Scale.X * circle.HorizontalRadius);

            return Vector2.DistanceSquared(point, circle.Center) <= radius * radius
                || rect.IsPointInside(circle.Center) || circle.IsPointInside(rect.Transform.TransformLocalToGlobal(Vector2.Zero));
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool RectLinedShapeIntersection(Rect rect, ILinedShape linedShape)
        {
            if (RectLinedGeometryIntersection(rect, linedShape))
                return true;

            return rect.IsPointInside(linedShape.StartPoint)
                || linedShape.IsPointInside(rect.Transform.TransformLocalToGlobal(Vector2.Zero));
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool RectLinedGeometryIntersection(Rect rect, ILinedGeometry linedGeometry)
        {
            foreach (Line l in linedGeometry.GetLines())
            {
                Vector2 start = rect.Transform.TransformGlobalToLocal(
                                linedGeometry.Transform.TransformLocalToGlobal(
                                    l.StartPoint
                                )
                            );
                Vector2 end = rect.Transform.TransformGlobalToLocal(
                                linedGeometry.Transform.TransformLocalToGlobal(
                                    l.EndPoint
                                )
                            );
                if (RectLineIntersection(rect, ref start, ref end))
                    return true;
            }
            return false;
        }
        /// <summary>
        /// Checks wether a Rectangle intersects with a Line by using the Liang-Barsky line clipping algorithm  
        /// </summary>
        /// <param name="rect"></param>
        /// <param name="line"></param>
        /// <returns></returns>
        public static bool RectLineIntersection(Rect rect, Line line)
        {
            Vector2 startPoint = rect.Transform.TransformGlobalToLocal(line.StartPoint);
            Vector2 endPoint = rect.Transform.TransformGlobalToLocal(line.EndPoint);
            return RectLineIntersection(rect, ref startPoint, ref endPoint);
        }
        private static bool RectLineIntersection(Rect rect, ref Vector2 transformedLineStartPoint, ref Vector2 transformedLineEndPoint)
        {
            float t0 = 0.0f; 
            float t1 = 1.0f;
            Vector2 lineDelta = transformedLineEndPoint - transformedLineStartPoint;
            
            float p, q, r;

            for (int edge = 0; edge < 4; edge++)
            {
                switch(edge)
                {
                    case 0:
                        p = -lineDelta.X;
                        q = transformedLineStartPoint.X;
                        break;
                    case 1:
                        p = lineDelta.X;
                        q = (rect.Width - transformedLineStartPoint.X); 
                        break;
                    case 2:
                        p = -lineDelta.Y;
                        q = transformedLineStartPoint.Y;
                        break;
                    case 3:
                        p = lineDelta.Y;
                        q = (rect.Height - transformedLineStartPoint.Y);
                        break;
                    default:
                        throw new IndexOutOfRangeException();
                }
                // parallel line outside
                if (p == 0 && q < 0) 
                    return false;

                r = q / p;


                // clip
                if (p < 0)
                {
                    if (r > t1)
                        return false;
                    else if (r > t0)
                        t0 = r;
                }
                else if (p > 0)
                {
                    if (r < t0) 
                        return false;
                    else if (r < t1)
                        t1 = r;
                }
            }

            return true;

        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LineLineIntersection(Line lineA, Line lineB)
        {
            Vector2 startA = lineA.StartPoint;
            Vector2 endA = lineA.EndPoint;
            Vector2 startB = lineB.StartPoint;
            Vector2 endB = lineB.EndPoint;

            return LineLineIntersection(ref startA, ref endA, ref startB, ref endB);
        }
        private static bool LineLineIntersection(ref Vector2 startA, ref Vector2 endA, ref Vector2 startB, ref Vector2 endB)
        {
            Vector2 distA = endA - startA;
            Vector2 distB = endB - startB;

            Vector2 perpA = new Vector2(-distA.Y, distA.X);
            Vector2 perpB = new Vector2(-distB.Y, distB.X);

            float tA = Vector2.Dot(startA - startB, perpA) / Vector2.Dot(distB, perpA);
            float tB = Vector2.Dot(startB - startA, perpB) / Vector2.Dot(distA, perpB);

            bool intersect = tA >= 0 && tA <= 1 && tB > 0 && tB <= 1;
            return intersect;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LinedGeometryLineIntersection(ILinedGeometry linedGeometry, Line line)
        {
            foreach (Line l in linedGeometry.GetLines())
            {
                Vector2 startA = linedGeometry.Transform.TransformLocalToGlobal(l.StartPoint);
                Vector2 endA = linedGeometry.Transform.TransformLocalToGlobal(l.EndPoint);
                Vector2 startB = line.StartPoint;
                Vector2 endB = line.EndPoint;

                if (LineLineIntersection(ref startA, ref endA, ref startB, ref endB))
                    return true;
            }
            return false;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LineGeometryLinedGeometryIntersection(ILinedGeometry linedGeometryA, ILinedGeometry linedGeometryB)
        {
            foreach (Line lA in linedGeometryA.GetLines())
            {
                foreach (Line lB in linedGeometryB.GetLines())
                {
                    Vector2 startA = linedGeometryA.Transform.TransformLocalToGlobal(lA.StartPoint);
                    Vector2 endA = linedGeometryA.Transform.TransformLocalToGlobal(lA.EndPoint);
                    Vector2 startB = linedGeometryB.Transform.TransformLocalToGlobal(lB.StartPoint);
                    Vector2 endB = linedGeometryB.Transform.TransformLocalToGlobal(lB.EndPoint);

                    if (LineLineIntersection(ref startA, ref endA, ref startB, ref endB))
                        return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LinedShapeLineIntersection(ILinedShape linedShape, Line line)
        {
            // check line intersection
            if (LinedGeometryLineIntersection(linedShape, line))
                return true;

            // if there was no line intersection, the line could be inside the linedShape, so check one point.
            return linedShape.IsPointInside(line.StartPoint);
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LinedShapeLinedGeometryIntersection(ILinedShape linedShape, ILinedGeometry linedGeometry)
        {
            if (LineGeometryLinedGeometryIntersection(linedShape, linedGeometry))
                return true;

            // if there was no line strip intersection, the line could be inside the linedShape, so check one point.
            return linedShape.IsPointInside(linedGeometry.StartPoint);
        }
        /// <summary>
        /// Checks wether the two objects are intersecting, touching or one is inside of the other
        /// </summary>
        public static bool LinedShapeLinedShapeIntersection(ILinedShape linedShapeA, ILinedShape linedShapeB)
        {
            if (LineGeometryLinedGeometryIntersection(linedShapeA, linedShapeB))
                return true;

            // if there was no line intersection, the linedShapeA could be inside the linedShapeB, or the other way around.
            return linedShapeA.IsPointInside(linedShapeB.StartPoint)
                || linedShapeB.IsPointInside(linedShapeA.StartPoint);
        }

        #endregion
    }
}
